<template>
  <el-dialog v-model="dialogVisible" :append-to-body='true' :width="widthDialog" title="模型预览" @close="cleanup()">
    <div style="display: flex;">
      <div ref="containerRef"></div>
      <div
          style="display: flex; flex-direction: column;justify-content: space-evenly; align-items: center;margin-left: 1rem;">
        <el-text tag="b">
          <el-icon>
            <InfoFilled/>
          </el-icon>
          按住鼠标左键旋转
        </el-text>
        <el-text tag="b">
          <el-icon>
            <InfoFilled/>
          </el-icon>
          按住Ctrl进行平移
        </el-text>
        <el-text tag="b">
          <el-icon>
            <InfoFilled/>
          </el-icon>
          鼠标滚轮进行缩放
        </el-text>
      </div>
    </div>
  </el-dialog>
</template>

<script setup>
import {nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'
import {STLLoader} from 'three/examples/jsm/loaders/STLLoader.js'
import {OBJLoader} from "three/examples/jsm/loaders/OBJLoader.js";
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js";
import {useVModel} from "@vueuse/core";
import {InfoFilled} from "@element-plus/icons-vue";

const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  url: {
    type: String,
    default: ""
  }
})
const emit = defineEmits(['update:modelValue'])
const dialogVisible = useVModel(props, 'modelValue', emit)

const widthDialog = ref(0)

let camera, renderer, controls, scene, mesh
const containerRef = ref()
const init = () => {
  createRender()
  createScene()
  createCamera()
  loadSTL()
  createLight()
  createControls()
  render()
  nextTick(() => containerRef.value.appendChild(renderer.domElement))
}
const cleanup = () => {
  if (renderer) {
    const element = renderer.domElement
    element.parentNode.removeChild(element)
    renderer.dispose()
    renderer = null
  }
  if (controls) {
    controls.dispose()
    controls = null
  }
  scene = null
  camera = null
  mesh = null
}
watch(dialogVisible, val => {
  if (val && !renderer) {
    init()
  } else if (!val) {
    cleanup()
  }
})

const createScene = () => {
  scene = new THREE.Scene()
}

const loadSTL = () => {
  let strRegex = new RegExp(/\.(stl|gltf|obj)$/, 'i');
  let result = props.url.match(strRegex);
  let loader
  if (result[1] === 'stl') {
    loader = new STLLoader()
    loader.load(
        props.url,
        geometry => {
          const material = new THREE.MeshLambertMaterial({color: 0xB8B8B8})
          mesh = new THREE.Mesh(geometry, material)
          mesh.rotation.x = -0.5 * Math.PI
          mesh.scale.set(0.9, 0.9, 0.9)
          scene.add(mesh)
          console.log(mesh)
        }
    )
  } else if (result[1] === 'obj') {
    loader = new OBJLoader();
    loader.load(
        props.url,
        geometry => {
          geometry.rotation.x = -0.5 * Math.PI
          geometry.scale.set(0.9, 0.9, 0.9)
          scene.add(geometry);
          console.log(geometry)
        }
    )
  } else {
    loader = new GLTFLoader();
    loader.load(
        props.url,
        geometry => {
          geometry.scene.rotation.x = -0.5 * Math.PI
          geometry.scene.scale.set(0.9, 0.9, 0.9)
          scene.add(geometry.scene);
          console.log(geometry)
        }
    )
  }
}

const createLight = () => {
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.3)
  scene.add(ambientLight)
  const spotLight = new THREE.SpotLight(0xffffff)
  spotLight.position.set(150, 150, 150)
  spotLight.castShadow = true
  scene.add(spotLight)
}

const createCamera = () => {
  widthDialog.value = window.innerHeight * 0.8 + 180
  const width = window.innerHeight * 0.8
  const height = window.innerHeight * 0.8
  const k = width / height
  camera = new THREE.PerspectiveCamera(35, k, 0.1, 1000)
  camera.position.set(0, 500, 0)
  camera.lookAt(new THREE.Vector3(10, 40, 0))
  scene.add(camera)
}

const createRender = () => {
  renderer = new THREE.WebGLRenderer({antialias: true, alpha: true})
  renderer.setSize(window.innerHeight * 0.8, window.innerHeight * 0.8)
  renderer.shadowMap.enabled = true
  renderer.shadowMap.type = THREE.PCFSoftShadowMap
  renderer.setClearColor(0xEFEFF0, 1)
}

const render = () => {
  if (renderer && camera) {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
  }
}

const createControls = () => {
  controls = new OrbitControls(camera, renderer.domElement)
}

onMounted(() => {
  if (dialogVisible.value) {
    init()
  }
})

onUnmounted(() => {
  cleanup()
})
</script>

<style>
#container {
  height: 100%;
  width: 100%;
}
</style>
